package uk.ac.glasgow.demosocs.impl;

import java.io.Serializable;
import java.util.Date;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Set;

import uk.ac.glasgow.demosocs.Candidate;
import uk.ac.glasgow.demosocs.Election;
import uk.ac.glasgow.demosocs.ElectionResult;
import uk.ac.glasgow.demosocs.ElectionState;
import uk.ac.glasgow.demosocs.impl.electionTypes.ElectoralSystem;
import uk.ac.glasgow.demosocs.impl.electionTypes.ElectoralSystemFactory;

/**
 * @author Tsvetelina Valcheva, Bilal Hamid
 * @version 1.2
 * 
 * This is Team E's implementation of the Election interface. It
 * represents an instance of election.
 * 
 * This class implements Election and Serializable.
 * 
 */

public class ElectionImpl implements Election, Serializable {

	
	private ElectionState state;
	private ElectoralSystem electoralSystem; 
	private Set<Candidate> candidates; // list of approved candidates
	private Set<Candidate> unapprovedCandidates;
	private Date openNominationsDate, startDate, endDate;
	private String EID, electionName, electionType, electorateID;
	private static long idCounter;
	private static final long serialVersionUID = 1L;
	
	
	/**
	 * Constructor to create a new election. Once an election is created it is
	 * assigned an election id, along with initalising the nominations list and
	 * setting the election state.
	 * 
	 * @param name
	 *            The name of the election, e.g. Level3-2012 Election
	 * @param type
	 *            The type of an elections e.g. First past the post or AV
	 * @param id
	 * 		 Unique id that identifies the election.
	 * @throws InvalidElectorateSystem 
	 */
	public ElectionImpl(String name, String type, int id) throws InvalidElectorateSystem {
		idCounter = id;
		electionName = name;
		electionType = type;
		electorateID = null;
		EID = createID();
		state = ElectionState.NOT_STARTED;
		candidates = new HashSet<Candidate>();
		openNominationsDate = null;
		startDate = null;
		endDate = null;
		unapprovedCandidates = new HashSet<Candidate>();
		this.electoralSystem = ElectoralSystemFactory.getInstance().createElectoralSystem(type, candidates);
     }

	
	/**
	 * This method edits an election, if an invalid date is given,
	 * the method will assign that date to the current date.
	 * 
	 * @param openNom
	 * 			The date when nominations start.
	 * @param start
	 * 			The start date for an election.
	 * @param stop
	 * 			The end Date for an election.
	 * @param electorateID
	 * 			The election id for an election e.g. EID1
	 */
	public void editElection(Date openNom, Date start, Date stop, String electorateID) {
		setStartDate(start);
		setEndDate(stop);
		setOpenNominationsDate(openNom);
		this.electorateID = electorateID;
		
	}

	public String getElectionType() {
		return electionType;
	}

	public void setElectionType(String electionType) {
		this.electionType = electionType;
	}

	/**
	 * Sets the election state for an election.
	 * @param Election state
	 */
	public void setElectionState(ElectionState s) {
		state = s;
	}

	/**
	 * Sets open nominations date, also checks for invalid
	 * dates. If date is invalid set the nominate date to the
	 * current date.
	 * 
	 * @param openNominations
	 */
	private void setOpenNominationsDate(Date openNominations) {
		if (openNominations.before(startDate) || openNominations.after(endDate))
			openNominationsDate = new Date();
		else
			openNominationsDate = openNominations;
		openNominationsDate = openNominations;
	}

	/**
	 * Sets open start date, also checks for invalid
	 * dates. If date is invalid set the start date to the
	 * current date.
	 * 
	 * @param startDate
	 */

	private void setStartDate(Date start) {
		Date currentDate = new Date();
		if (start.before(currentDate))
			startDate = currentDate;
		else
			startDate = start;
	}

	/**
	 * Sets end date, also checks for invalid
	 * dates. If date is invalid set the end date to the
	 * current date.
	 * 
	 * @param endDate
	 */
	private void setEndDate(Date end) {
		if (end.before(startDate))
			endDate = new Date();
		else
			endDate = end;
	}

	/**
	 * Getters
	 * */

	/**
	 * @return the collection of confirmed candidates in the election.
	 */
	public Set<Candidate> getCandidates() {
		return candidates;
	}

	/**
	 * @return the election unique identifier
	 */
	public String getEID() {
		return EID;
	}

	/**
	 * Finds the election winner/winners. At the moment this method only
	 * finds results for first past the post, but will later handle referendum's and
	 * the AV voting system.
	 * 
	 * @return ElectionResult - the result of the election.
	 * 			null - If there are no votes cast in the election.
	 */
	public ElectionResult getElectionResult() {
		return electoralSystem.getElectionResult();
	}

	/***
	 * @return the state of the election.
	 */
	public ElectionState getElectionState() {
		return state;
	}

	/**
	 * @return the date the election is <i>scheduled</i> to end.
	 */

	public Date getEnd() {
		return endDate;
	}

	/**
	 * @return the date the election is <i>scheduled</i> to open nominations.
	 */
	public Date getOpenNominations() {
		return openNominationsDate;
	}

	/**
	 * @return the date the election is <i>scheduled</i> to start.
	 */
	public Date getStart() {
		return startDate;
	}

	/**
	 * Utility method to generate unique election id's.
	 * @return electionID
	 * 			String containing the id, e.g. EID0
	 */
	private static String createID() {
		return "EID" + String.valueOf(idCounter++);
	}

	/**
	 * Returns a list of the unapproved candidates
	 * @return
	 */
	public Set<Candidate> getUnapprovedCandidates() {
		return unapprovedCandidates;
	}

	/**
	 * Sets the unapproved candidate list.
	 * @return
	 */
	public void setUnapprovedCandidates(Set<Candidate> unapprovedCandidates) {
		this.unapprovedCandidates = unapprovedCandidates;
	}

	public String getElectorateID() {
		return electorateID;
	}

	public void setElectorateID(String electorateID) {
		this.electorateID = electorateID;
	}
	
	
	public ElectoralSystem getElectoralSystem() {
		return electoralSystem;
	}

	public void setElectoralSystem(ElectoralSystem electoralSystem) {
		this.electoralSystem = electoralSystem;
	}
	

}